"""Authentication service."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
import hashlib
import logging
import secrets

from fastapi import HTTPException, status
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)

from ..core.config import settings
from ..core.security import (
    create_access_token,
    decode_token,
    get_password_hash,
    verify_password,
)
from ..models.user import DEFAULT_ROLE, USER_ROLES, User
from ..repositories.password_reset_repository import PasswordResetRepository
from ..repositories.user_repository import UserRepository
from ..schemas.password_reset import PasswordResetCreate
from ..schemas.token import TokenPair
from ..schemas.user import UserCreate
from ..services.audit_service import AuditService
from ..services.email_service import EmailService


class AuthService:
    def __init__(self, session: Session) -> None:
        self.session = session
        self.repo = UserRepository(session)
        self.reset_repo = PasswordResetRepository(session)
        self.audit = AuditService(session)
        self.email = EmailService()
        self._last_reset_token: tuple[str, str] | None = None

    def authenticate(self, identifier: str, password: str, remember: bool = False) -> TokenPair:
        # 尝试使用邮箱登录，若失败则回退为姓名匹配
        user = self.repo.get_by_email(identifier)
        logger.info(f"Login attempt: identifier={identifier}, found_by_email={user is not None}, remember={remember}")
        if not user:
            # 支持使用 full_name 登录（前端字段称为 username）
            user = self.repo.get_by_name(identifier)
            logger.info(f"Fallback to name lookup: found={user is not None}")
        if not user:
            logger.warning(f"Login failed: user not found for identifier={identifier}")
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials"
            )

        password_valid = verify_password(password, user.hashed_password)
        logger.info(f"Password verification: user={user.email}, valid={password_valid}")

        if not password_valid:
            logger.warning(f"Login failed: invalid password for user={user.email}")
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials"
            )
        if not user.is_active:
            logger.warning(f"Login failed: inactive user={user.email}")
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is inactive")
        token_pair = self._create_tokens(user, remember=remember)
        self.audit.record(actor_id=user.id, action="auth.login", payload_digest=f"remember={remember}")
        logger.info(f"Login successful: user={user.email}, remember={remember}")
        return token_pair

    def register(self, data: UserCreate) -> User:
        existing = self.repo.get_by_email(data.email)
        if existing:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
            )
        role = data.role if data.role in USER_ROLES else DEFAULT_ROLE
        user = User(
            email=data.email,
            hashed_password=get_password_hash(data.password),
            full_name=data.full_name,
            role=role,
            is_admin=role == "admin",
        )
        self.session.add(user)
        self.session.flush()
        self.audit.record(actor_id=user.id, action="auth.register")
        return user

    def refresh(self, refresh_token: str) -> TokenPair:
        payload = decode_token(refresh_token, expected_type="refresh")
        user_id = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token"
            )
        user = self.repo.get_by_id(int(user_id))
        if user is None or not user.is_active:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="User not available"
            )
        token_pair = self._create_tokens(user)
        self.audit.record(actor_id=user.id, action="auth.refresh")
        return token_pair

    def request_password_reset(self, email: str) -> None:
        user = self.repo.get_by_email(email)
        if not user or not user.is_active:
            self._last_reset_token = None
            return
        raw_token = secrets.token_urlsafe(32)
        expires_at = datetime.now(timezone.utc) + timedelta(
            minutes=settings.PASSWORD_RESET_TOKEN_EXPIRE_MINUTES
        )
        self.reset_repo.create(
            PasswordResetCreate(
                token=raw_token,
                expires_at=expires_at,
                user_id=user.id,
            )
        )
        digest = hashlib.sha256(raw_token.encode("utf-8")).hexdigest()
        self.audit.record(
            actor_id=user.id,
            action="auth.password_forgot",
            payload_digest=digest,
        )

        # Send password reset email (Synchronous)
        user_display_name = user.full_name or user.email.split("@", 1)[0]
        email_sent = self.email.send_password_reset_email(
            email_to=user.email, token=raw_token, user_name=user_display_name
        )

        if email_sent:
            logger.info(f"Password reset email sent to {user.email}")
        else:
            logger.warning(f"Failed to send password reset email to {user.email}")

        self._last_reset_token = (user.email, raw_token)

    def reset_password(self, token: str, new_password: str) -> None:
        now = datetime.now(timezone.utc)
        token_obj = self.reset_repo.get_valid_token(token, now)
        if not token_obj:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired token"
            )
        user = token_obj.user
        user.hashed_password = get_password_hash(new_password)
        token_obj.used = True
        self.session.flush()
        self.audit.record(actor_id=user.id, action="auth.password_reset")

    def reset_password_direct(self, email: str, new_password: str, confirm_password: str) -> None:
        """
        简化版密码重置：无邮件确认，直接重置.

        Args:
            email: 用户邮箱
            new_password: 新密码
            confirm_password: 确认密码

        Raises:
            HTTPException: 用户不存在、密码不一致等错误
        """
        # 验证两次密码一致
        if new_password != confirm_password:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="Passwords do not match"
            )

        # 查找用户
        user = self.repo.get_by_email(email)
        if not user:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")

        if not user.is_active:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is inactive")

        # 重置密码
        user.hashed_password = get_password_hash(new_password)
        self.session.flush()
        self.audit.record(actor_id=user.id, action="auth.password_reset_direct")

    def _create_tokens(self, user: User, remember: bool = False) -> TokenPair:
        # 如果勾选"记住我"，使用30天的token有效期
        if remember:
            access_expires = timedelta(days=settings.ACCESS_TOKEN_EXPIRE_DAYS_REMEMBER)
        else:
            access_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)

        refresh_expires = timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
        issued_at = datetime.now(timezone.utc)
        access_token = create_access_token(str(user.id), "access", access_expires)
        refresh_token = create_access_token(str(user.id), "refresh", refresh_expires)
        return TokenPair(
            access_token=access_token,
            refresh_token=refresh_token,
            expires_at=issued_at + access_expires,
        )
